package com.leetroll.app.annotation.OperationLogger;

import com.alibaba.fastjson.JSON;
import com.leetroll.api.global.SysConf;
import com.leetroll.api.service.UserService;
import com.leetroll.common.entity.OperateErrorLogInfo;
import com.leetroll.common.entity.OperateLogInfo;
import com.leetroll.common.entity.User;
import com.leetroll.common.holder.RequestHolder;
import com.leetroll.common.utils.*;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * 记录用户操作的错误日志
 */
@Aspect
@Component
@Slf4j
public class LoggerAspect {

    @Autowired
    RedisUtil redisUtil;

    @Autowired
    ThreadPoolTaskExecutor threadPoolTaskExecutor;

    @Autowired
    private UserService userService;

    /**
     * 开始日期
     **/
    Date beginDate;
    /**
     * 统计请求的处理时间
     */
    ThreadLocal<Long> startTime = new ThreadLocal<>();

    /**
     * @Author qdy
     * @Description 设置操作日志切入点 记录操作日志 在注解的位置切入代码
     * @Date 16:19 2024/4/11
     **/
    @Pointcut("@annotation(com.leetroll.app.annotation.OperationLogger.OperationLogger)")
    public void logPoinCut() {
    }

    /**
     * @Author qdy
     * @Description 设置操作异常切入点记录异常日志 扫描所有controller包下操作
     * @Date 16:21 2024/4/11
     **/
    @Pointcut("execution(* com.leetroll.app.controller..*.*(..))")
    public void exceptionLogPoinCut() {
    }

    @Before("logPoinCut()")
    public void doBefore() {
        // 接收到请求，记录请求开始时间
        startTime.set(System.currentTimeMillis());
    }

    @Pointcut(value = "@annotation(operationLogger)")
    public void pointcut(OperationLogger operationLogger) {
    }

//    @Around(value = "pointcut(operationLogger)", argNames = "joinPoint,operationLogger")
//    public Object doAround(ProceedingJoinPoint joinPoint, OperationLogger operationLogger) throws Throwable {
//        //先执行业务
//        Object result = joinPoint.proceed();
//        try {
//            // 日志收集
//            handle(joinPoint);
//        } catch (Exception e) {
//            log.error("日志记录出错!", e);
//        }
//        return result;
//    }

    /**
     * @Author qdy
     * @Description  常返回通知，拦截用户操作日志，连接点正常执行完成后执行， 如果连接点抛出异常，则不会执行
     * @Date 15:20 2024/4/11
     * @Param [joinPoint, keys]
     **/
    @AfterReturning(value = "logPoinCut()", returning = "keys")
    public void doAfterReturning(JoinPoint joinPoint, Object keys) {
        // 获取RequestAttributes
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        // 从获取RequestAttributes中获取HttpServletRequest的信息
        assert requestAttributes != null;
        HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);

        //用户登陆用户名
        Long userId= UserUtil.getUserId();
        User user = userService.getUser(userId);
        String userName =null;
        if(user != null){
            userName = user.getName();
        }

        OperateLogInfo logInfo = new OperateLogInfo();
        try {
            // 从切面织入点处通过反射机制获取织入点处的方法
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            // 获取切入点所在的方法
            Method method = signature.getMethod();
            // 获取请求的类名
            String className = joinPoint.getTarget().getClass().getName();
            // 获取操作
            OperationLogger log = method.getAnnotation(OperationLogger.class);
            if (Objects.nonNull(log)) {
                logInfo.setModule(log.modul().getContent());
                logInfo.setType(log.type());
                logInfo.setMessage(log.desc());
            }
            // 请求的方法名
            logInfo.setMethod(className + "." + method.getName());
            assert request != null;
            // 请求参数
            logInfo.setReqParam(JSON.toJSONString(converMap(request.getParameterMap())));
            // 返回结果
            logInfo.setResParam(JSON.toJSONString(keys));
            // 请求IP
            logInfo.setIp(IpUtils.getIpAddr(request));
            // 请求URI
            logInfo.setUrl(request.getRequestURI());
            // 耗时
            logInfo.setTakeupTime((System.currentTimeMillis() - startTime.get()));

            logInfo.setUserName(userName);
            logInfo.setUserId(userId);

            logInfo.insert();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * @Author qdy
     * @Description 异常返回通知，用于拦截异常日志信息 连接点抛出异常后执行
     * @Date 15:13 2024/4/11
     * @Param [joinPoint, operationLogger, e]
     **/
    @AfterThrowing(pointcut = "exceptionLogPoinCut()", throwing = "e")
//    @AfterThrowing(value = "pointcut(operationLogger)", throwing = "e", argNames = "joinPoint,operationLogger,e")
    public void doAfterThrowing(JoinPoint joinPoint,Throwable e) throws Exception {
//    public void doAfterThrowing(JoinPoint joinPoint, OperationLogger operationLogger, Throwable e) throws Exception {

        //用户登陆用户名
        Long userId= UserUtil.getUserId();
        User user = userService.getUser(userId);
        String userName =null;
        if(user != null){
            userName = user.getName();
        }

        //系统异常日志
        OperateErrorLogInfo exception = new OperateErrorLogInfo();
        HttpServletRequest request = RequestHolder.getRequest();
        assert request != null;
        //ip
        String ip = IpUtils.getIpAddr(request);
        //请求地址
        String requestURI = request.getRequestURI();
        //请求参数
        String paramsJson =JSON.toJSONString(converMap(request.getParameterMap()));
        String exceptionName = exception.getClass().getName();
        String message =stackTraceToString(e.getClass().getName(), e.getMessage(), e.getStackTrace());

        //设置请求信息
        exception.setIp(ip);
        //设置调用的方法
        exception.setMethod(joinPoint.getSignature().getName());
        exception.setExceptionMessage(message);
        exception.setExceptionName(exceptionName);
        exception.setReqParam(paramsJson);
        exception.setUrl(requestURI);

        exception.setUserName(userName);
        exception.setUserId(userId);

        //插入数据库
        exception.insert();
    }


    /**
     * 日志收集
     */
    private void handle(ProceedingJoinPoint point) throws Exception {
        HttpServletRequest request = RequestHolder.getRequest();
        //获取当前方法
        Method currentMethod = AspectUtil.INSTANCE.getMethod(point);
        //获取操作名称
        OperationLogger annotation = currentMethod.getAnnotation(OperationLogger.class);
        //注解中是否将当前日志记录到数据库中
        boolean save = annotation.savePg();
        String bussinessName = getBusinessName(point,annotation);
        String ua = RequestUtil.getUa();

        assert request != null;
        log.info("{} | {} - {} {} - {}", bussinessName, IpUtils.getIpAddr(request), RequestUtil.getMethod(), RequestUtil.getRequestUrl(), ua);
        if (!save) {
            return;
        }
        // 获取参数名称和值
        Map<String, Object> nameAndArgsMap = AopUtils.getFieldsName(point);
        // 当前操作用户
        String userId = "";
        String userName = "";
        if (request.getAttribute(SysConf.USER_ID) != null) {
            userId = request.getAttribute(SysConf.USER_ID).toString();
        }
        if (request.getAttribute(SysConf.USER_NAME) != null) {
            userName = request.getAttribute(SysConf.USER_NAME).toString();
        }
        String paramsJson = mapToString(nameAndArgsMap);
        String type = request.getMethod();
        String ip = IpUtils.getIpAddr(request);
        String url = request.getRequestURI();

        // 异步存储日志
        threadPoolTaskExecutor.execute(
                new SysLogHandle(ip, type, url, userId,userName,
                        paramsJson, point.getTarget().getClass().getName(),
                        point.getSignature().getName(), bussinessName,
                        beginDate, redisUtil));
    }

    public String mapToString(Map<String, Object> map){
        StringBuilder sb = new StringBuilder();
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            sb.append(entry.getKey()).append("=").append(entry.getValue()).append(", ");
        }
        // 移除最后的逗号和空格
        return sb.substring(0, sb.length() - 2);
    }

    public String getBusinessName(ProceedingJoinPoint point,OperationLogger annotation){
        return AspectUtil.INSTANCE.parseParams(point.getArgs(), annotation.desc());
    }

    public String stackTraceToString(String exceptionName, String exceptionMessage, StackTraceElement[] elements) {
        StringBuilder strbuff = new StringBuilder();
        for (StackTraceElement stet : elements) {
            strbuff.append(stet).append("<br/>");
        }
        return exceptionName + ":" + exceptionMessage + "<br/>" + strbuff.toString();
    }
    public Map<String, String> converMap(Map<String, String[]> paramMap) {
        Map<String, String> rtnMap = new HashMap<String, String>();
        for (String key : paramMap.keySet()) {
            rtnMap.put(key, paramMap.get(key)[0]);
        }
        return rtnMap;
    }

}
